﻿using System.Threading.Tasks;
using System;

namespace DotNetImage.Imaging
{
    /// <summary>
    /// http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/a2ad770b-b350-479f-9865-0d5c129a1377
    /// 
    /// http://msdn.microsoft.com/en-us/library/aa970271.aspx
    /// 
    /// http://social.msdn.microsoft.com/forums/en-US/wpf/thread/580fca03-d15b-48b2-9fb3-2e6fec8a2c68/
    /// 
    /// http://xiu.shoeke.com/2010/07/15/resizing-images-with-wpf-4-0/
    /// 
    /// http://www.codeguru.com/csharp/.net/net_wpf/article.php/c12221/Windows-Presentations-Foundation-WPF--2D-Transformations.htm
    /// 
    /// http://wpftutorial.net/Images.html
    /// </summary>
    public abstract class FilterBase
    {
        /// <summary>
        /// When True - multithreaded version is called, otherwise - the linear one.
        /// </summary>
        public bool HasMultiThreadingSupport { get; protected set; }

        /// <summary>
        /// When true, then after filter application internal properties has to be refreshed (width, height, pixel map).
        /// It may happen for example in case of rotation or resize, i.e. any time, when dimensions are changed.
        /// </summary>
        public bool RequiresInternalRefresh { get; protected set; }

        protected const byte MAX_BYTE_VALUE = byte.MaxValue;

        /// <summary>
        /// Number of pixels in the stride
        /// </summary>
        protected uint nWidth = 0;

        /// <summary>
        /// Index of the x pixel, the processing will start from. Used by matrix transformation.
        /// For example in case 3x3 matrix one left, one right, one top and one bottom lines
        /// are not being processed.
        /// </summary>
        protected uint startX = 0;

        /// <summary>
        /// Number of rows to be processed
        /// </summary>
        protected uint nHeight = 0;

        /// <summary>
        /// Index of the y pixel, the processing will start from. Used by matrix transformation.
        /// For example in case 3x3 matrix one left, one right, one top and one bottom lines
        /// are not being processed.
        /// </summary>
        protected uint startY = 0;

        /// <summary>
        /// Image being processed.
        /// </summary>
        protected DotNetImage Subject { get; private set; }

        protected FilterBase()
        {
            HasMultiThreadingSupport = true;
            RequiresInternalRefresh = false;
        }

        public void Apply(DotNetImage bmp)
        {
            Subject = bmp;
            ValidateArguments();
            PrepareData(bmp);
            if (HasMultiThreadingSupport)
            {
                ProcessMT(bmp);
            }
            else
            {
                ProcessPerPixel(bmp);
            }
            PostProcessing(bmp);
        }


        /// <summary>
        /// Each filter has to implement its own argument validation
        /// </summary>
        protected virtual void ValidateArguments(){}

        /// <summary>
        /// Used to prepare data, such as gamma matrixes or convolution matrixes, before applying transformation.
        /// </summary>
        protected virtual void PrepareData(DotNetImage bmp)
        {
            nHeight = (uint)bmp.Height;
            nWidth = (uint)bmp.Width;
        }

        /// <summary>
        /// Used to do some post processing, when pixel-by-pixel processing is completed. See Moire for example.
        /// </summary>
        /// <param name="bmp"></param>
        protected virtual void PostProcessing(DotNetImage bmp) { }


        /// <summary>
        /// Linear processing, pixel by pixel, single threaded.
        /// </summary>
        /// <param name="bmp"></param>
        protected virtual void ProcessPerPixel(DotNetImage bmp)
        {
            // Symmetric processing
            for (uint y = startY; y < nHeight - startY; y++)
            {
                for (uint x = startX; x < nWidth - startX; x++)
                {
                    Pixel pix = bmp.GetPixel(x, y);
                    ProcessPixel(ref pix, x, y);
                    bmp.SetPixel(pix, x, y);
                }
            }
        }

        /// <summary>
        /// Multithreaded processing. Threads are created on per row basics. 
        /// If extra intelligent processing required - override this implementation.
        /// </summary>
        /// <param name="bmp"></param>
        protected virtual void ProcessMT(DotNetImage bmp)
        {
            Parallel.For(startY, nHeight - startY, y =>
            {
                for (uint x = startX; x < nWidth - startX; x++)
                {
                    Pixel pix = bmp.GetPixel(x, (uint)y);
                    ProcessPixel(ref pix,
                                 x,
                                 (uint)y);
                    bmp.SetPixel(pix, x, (uint)y);
                }
            });
        }

        /// <summary>
        /// If it's possible to extract single pixel processing logic, then it should be placed here.
        /// </summary>
        /// <param name="pixel"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        protected virtual void ProcessPixel(ref Pixel pix, uint x = 0, uint y = 0)
        {
            throw new NotImplementedException();
        }
    }
}
